﻿using System.ComponentModel;
using Dyao.Book.Application.Abstractions;
using Dyao.Book.Contract;
using Dyao.Book.Entity;
using Dyao.Book.Repository.Abstractions;
using Foundation.Core;
using Foundation.Core.ObjectMapping;
using Foundation.IdCreate;
using Foundation.Spider;
using Foundation.Spider.DataFlow;
using Foundation.Spider.DataFlow.Parser;
using Foundation.Spider.Http;
using Foundation.Uow;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Dyao.Book.Application
{
    [DisplayName("CrawBookContentSpider")]
    public class CrawlBookContentSpider : Spider, ICrawlBookContentSpider
    {
        private readonly ISpiderManager _spiderMgr;

        private readonly IJsonSerializer _serializer;

        public CrawlBookContentSpider(IOptions<SpiderOptions> options,
        DependenceServices services,
        ILogger<CrawlBookContentSpider> logger,
        IJsonSerializer serializer,
        ISpiderManager spiderMgr) : base(options, services, logger)
        {
            _spiderMgr = spiderMgr;
            _serializer = serializer;
        }

        protected override async Task InitializeAsync(CancellationToken stoppingToken = default)
        {
            // 添加自定义解析
            AddDataFlow<MatchBookContentSourceDataParser>();
            AddDataFlow<ConsoleStorage>();

            // 错误信息
            OnRequestError += (request, response) =>
            {
                var bookProp = (CrawCatalogueContentDto)request.Properties.GetOrDefault(QiDianConstant.CatalogueContentRequestBookProp);
                string message = $"Uri:{request.RequestUri}, ReasonPhrase:{response.ReasonPhrase}";
                _spiderMgr.PushMessageAsync(bookProp.BookCatalogueId, OperationTypeEnum.ChapterContent, message);
            };

            // 超时请求
            OnRequestTimeout += (requests) =>
            {
                foreach (var request in requests)
                {
                    var bookProp = (CrawCatalogueContentDto)request.Properties.GetOrDefault(QiDianConstant.CatalogueContentRequestBookProp);
                    string message = $"Uri:{request.RequestUri} RequestTimeout";
                    _spiderMgr.PushMessageAsync(bookProp.BookCatalogueId, OperationTypeEnum.ChapterContent, message);
                }
            };
            await Task.CompletedTask;
        }

        /// <inheritdoc cref="ICrawlBookContentSpider.CrawlContentByCataloguesAsync"/>
        public async Task<bool> CrawlContentByCataloguesAsync(List<CrawCatalogueContentDto> dtos, BookSourceWarehouseDto setting, CancellationToken cancellationToken)
        {
            if (dtos.IsNullOrEmpty())
                return false;

            var requests = new List<Request>();
            foreach (var dto in dtos)
            {
                var request = BuildRequest(dto, setting);
                requests.Add(request);
            }

            await ExecuteAsync(cancellationToken, async () =>
            {
                await AddRequestsAsync(requests);
            });

            return true;
        }

        public Request BuildRequest(CrawCatalogueContentDto dto, BookSourceWarehouseDto setting)
        {
            var request = new Request(dto.ContentLink)
            {
                // 请求超时10秒
                Timeout = 10000
            };

            if (!setting.Header.IsNullOrEmpty())
            {
                var header = _serializer.Deserialize<BookSourceWarehouseHeaderDto>(setting.Header);
                if (!header.UserAgent.IsNullOrEmpty())
                    request.Headers.UserAgent = header.UserAgent;

                if (!header.Cookies.IsNullOrEmpty())
                    request.Headers.Cookie = header.Cookies;
            }

            var contentRule = _serializer.Deserialize<BookSourceWarehouseSpiderRuleDto>(setting.SpiderRule);

            request.Properties.Add(QiDianConstant.CatalogueContentRequestBookProp, dto);
            request.Properties.Add(QiDianConstant.BookContentTargetTypeRequestProp, contentRule.BookContentSpiderRule);

            return request;
        }
    }


    public class MatchBookContentSourceDataParser : DataParser
    {
        public override Task InitializeAsync()
        {
            return Task.CompletedTask;
        }

        protected override async Task ParseAsync(DataFlowContext context)
        {
            var logger = context.ServiceProvider.GetService<ILogger<MatchBookContentSourceDataParser>>();
            var contentProp = (CrawCatalogueContentDto)context.Request.Properties.GetOrDefault(QiDianConstant.CatalogueContentRequestBookProp);
            var ruleProp = (BookSourceWarehouseBookContentSpiderRuleDto)context.Request.Properties.GetOrDefault(QiDianConstant.BookContentTargetTypeRequestProp);
            var spiderMgr = context.ServiceProvider.GetService<ISpiderManager>();
            var objectMapper = context.ServiceProvider.GetService<IObjectMapper>();
            try
            {
                var bookCatalogues = new List<BookCatalogueContentEntity>();

                using (var scope = context.ServiceProvider.CreateScope())
                {
                    var serviceProvider = scope.ServiceProvider;
                    var builder = new BusinessChainBuilder<BookContentParsePluginsChain, BookContentParsePluginsContext>(serviceProvider);
                    var args = new BookContentParsePluginsArgs()
                    {
                        Chapter = contentProp,
                        SpiderRule = ruleProp,
                    };

                    builder.UsePlugin(typeof(HtmlContentParsePlugins));

                    var chain = builder.Build();
                    var parseContext = new BookContentParsePluginsContext(args)
                    {
                        Input = context
                    };
                    await chain.RunAsync(parseContext, default);

                    if (!parseContext.Output.IsNullOrEmpty())
                        bookCatalogues = objectMapper.Map<List<ChapterContentParseDto>, List<BookCatalogueContentEntity>>(parseContext.Output);
                }

                if (bookCatalogues.Count() <= 10)
                    throw new BusinessException($"章节编号:{contentProp.BookCatalogueId},Url:{context.Request.RequestUri.ToString()}, 内容获取错误.");

                await BulkAsync(context, bookCatalogues);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, ex.Message);
                await spiderMgr.PushMessageAsync(contentProp.BookCatalogueId, OperationTypeEnum.ChapterContent, $"Parse Error:{ex.Message.ToString()},Trace:{ex.GetTraceMessage()}");
            }
        }


        public async Task BulkAsync(DataFlowContext context, List<BookCatalogueContentEntity> contents)
        {
            CancellationToken cancellationToken = default;
            var contentProp = (CrawCatalogueContentDto)context.Request.Properties.GetOrDefault(QiDianConstant.CatalogueContentRequestBookProp);
            var uowMgr = context.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
            var logger = context.ServiceProvider.GetRequiredService<ILogger<MatchBookContentSourceDataParser>>();
            var bookCatalogueContentRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueContentRepository>();
            var bookCatalogueRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueRepository>();
            var commonMgr = context.ServiceProvider.GetRequiredService<ICommonManager>();

            if (!contents.Any()) return;
            logger.LogInformation($"开始添加，数量{contents.Count()}");

            var catalogues = new List<BookCatalogueEntity>()
                    {
                        new BookCatalogueEntity(){
                            ID = contentProp.BookCatalogueId,
                            CrawlComplete = true
                        }
                    };

            var log = new OperationLogDto()
            {
                OperationLog = $"爬取正文内容,来源:{contentProp.SourceKey},正文链接:{contentProp.ContentLink}",
                SourceId = contentProp.BookCatalogueId,
                OperationType = OperationTypeEnum.Chapter
            };

            using (var uow = uowMgr.Begin(isTransactional: true, requiresNew: true))
            {
                try
                {
                    await bookCatalogueRepo.BulkUpdateAsync(catalogues, op => new { op.CrawlComplete }, cancellationToken); // 更新爬虫标志
                    // 先删除再插入
                    var delEntites = new List<BookCatalogueContentEntity>()
                    {
                        new BookCatalogueContentEntity(){
                            BookCatalogueId = contentProp.BookCatalogueId,
                        }
                    };
                    await bookCatalogueContentRepo.BulkDeleteAsync(delEntites, bc => new { bc.BookCatalogueId }, cancellationToken);
                    await bookCatalogueContentRepo.BulkInsertAsync(contents, cancellationToken);
                    await commonMgr.AddOperationLogsAsync(log);
                    await uow.CompleteAsync(cancellationToken);
                    logger.LogInformation($"添加成功，数量{contents.Count()}");
                }
                catch (Exception ex)
                {
                    logger.LogError($"添加失败，{ex.Message}", ex);
                    await uow.RollbackAsync();
                    throw;
                }
            }
        }
    }
}
